package ls.vuecp.gui.component;

import static tufts.Util.TERM_CLEAR;
import static tufts.Util.TERM_PURPLE;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import ls.vuecp.gui.draw.DrawContext;

import tufts.Util;
import tufts.vue.DEBUG;
import tufts.vue.Resource;
import tufts.vue.VueConstants;
import tufts.vue.VueResources;

//import tufts.vue.LWComponent;

public class LWComponent 
	implements VueConstants
{
	protected static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(LWComponent.class);

    public static final Object PROPERTY_SET_DEFAULT = "default";
    public static final Object PROPERTY_SET_UNDO = "undo";
    
	public LWComponent()
    {
    }
	
	protected float x;
    protected float y;
    
    protected transient LWContainer parent;
    public static final java.util.List<LWComponent> NO_CHILDREN = Collections.EMPTY_LIST;

    
    /** creation time-stamp (when this node first joined a map) */
    private long mCreated;
    
	public String label = null; // protected for debugging purposes
    private String notes = null;
    private Resource resource = null;
    private String mLabelFormat; // if ther
    
	protected transient TextBox labelBox = null;
	
	private transient long mSupportedPropertyKeys;
	public final ColorProperty mFillColor = new ColorProperty(KEY_FillColor);
    public final ColorProperty mTextColor = new ColorProperty(KEY_TextColor, java.awt.Color.black) {
            //{ color = java.awt.Color.black; } // default value
            void onChange() {
//                if (labelBox != null)
//                    labelBox.copyStyle(LWComponent.this); // todo better: handle thru style.textColor notification?
            }
        };
	public final ColorProperty mStrokeColor = new ColorProperty(KEY_StrokeColor, java.awt.Color.darkGray);
    public final FloatProperty mStrokeWidth = new FloatProperty(KEY_StrokeWidth) 
    { 
    	void onChange() { 
//    		rebuildStroke(); 
    	}
    };
    public final EnumProperty<Alignment> mAlignment = new EnumProperty(KEY_Alignment, Alignment.LEFT) {
            void onChange() { 
//            	layout(KEY_Alignment); 
            }
        };

    public final EnumProperty<StrokeStyle> mStrokeStyle = new EnumProperty(KEY_StrokeStyle, StrokeStyle.SOLID) { 
    	void onChange() { 
//    		rebuildStroke(); 
    		}
    	};

	
	public static final Key KEY_FillColor   = new Key("fill.color", "background")       { final Property getSlot(LWComponent c) { return c.mFillColor; } };
    public static final Key KEY_TextColor   = new Key("text.color", "font-color")       { final Property getSlot(LWComponent c) { return c.mTextColor; } };
    public static final Key KEY_StrokeColor = new Key("stroke.color", "border-color")   { final Property getSlot(LWComponent c) { return c.mStrokeColor; } };
    //public static final Key KEY_StrokeStyle = new Key("stroke.style", "border-style")   { final Property getSlot(LWComponent c) { return null; } };
    public static final Key KEY_StrokeWidth = new Key("stroke.width", "stroke-width")   { final Property getSlot(LWComponent c) { return c.mStrokeWidth; } };
    public static final Key KEY_StrokeStyle = new Key<LWComponent,StrokeStyle>
        ("stroke.style", KeyType.STYLE)   { final Property getSlot(LWComponent c) { return c.mStrokeStyle; } };
    public static final Key KEY_Alignment = new Key<LWComponent,Alignment>
        ("alignment", KeyType.STYLE)   { final Property getSlot(LWComponent c) { return c.mAlignment; } };

        public final IntProperty mFontStyle = new CSSFontStyleProperty(KEY_FontStyle)       { 
        	void onChange() { 
//        		rebuildFont(); 
        		} 
        	};
        public final IntProperty mFontSize = new IntProperty(KEY_FontSize)                  { 
        	void onChange() { 
//        		rebuildFont(); 
        		} 
        	};
        public final StringProperty mFontName = new CSSFontFamilyProperty(KEY_FontName)     { 
        	void onChange() { 
//        		rebuildFont(); 
        		} 
        	};

        public final FontProperty mFont = new FontProperty(KEY_Font) {
            @Override void onChange() {
//                if (!fontIsRebuilding) {
//                    final Font f = get();
//
//                    mFontStyle.take(f.getStyle());
//
//                    mFontSize.take(f.getSize());
//                    mFontName.take(f.getName());
//                }
//
//                if (labelBox != null) {
//                    labelBox.copyStyle(LWComponent.this);
//                    layout(this.key); // could make this generic: add a key bit that says "layout needed on-change";
//                }
            }
        };
        public final StringProperty mFontUnderline = new StringProperty(KEY_FontUnderline) {
        	boolean isChanged(String newValue) {
        		return true;
        	}

        	@Override void onChange() {
//                rebuildFont();
//                if (labelBox != null) {
//                    labelBox.copyStyle(LWComponent.this);
//                    layout(this.key); // could make this generic: add a key bit that says "layout needed on-change";
//                }
        	}

        };
        	
    /** Aggregate font key, which represents the combination of it's three sub-properties */
    public static final Key KEY_Font = new Key("font", KeyType.STYLE)                   { final Property getSlot(LWComponent c) { return c.mFont; } };
    public static final Key KEY_FontSize  = new Key("font.size", KeyType.SUB_STYLE)     { final Property getSlot(LWComponent c) { return c.mFontSize; } };
    public static final Key KEY_FontStyle = new Key("font.style", KeyType.SUB_STYLE)    { final Property getSlot(LWComponent c) { return c.mFontStyle; } };
    public static final Key KEY_FontUnderline = new Key("font.underline", KeyType.SUB_STYLE)    { final Property getSlot(LWComponent c) { return c.mFontUnderline; } };
    public static final Key KEY_FontName  = new Key("font.name", KeyType.SUB_STYLE)     { final Property getSlot(LWComponent c) { return c.mFontName; } };

    /** context codes for LWContainer.addChildren */

    public static final Object ADD_DROP = "drop";
    public static final Object ADD_PASTE = "paste";
    public static final Object ADD_DEFAULT = "default";
    public static final Object ADD_PRESORTED = "sorted";
    public static final Object ADD_MERGE = "merge";
    public static final Object ADD_CHILD_TO_SIBLING = "child-to-sibling";
    
    public static final String KEY_LabelFormat = "label.format";
    public static final Key KEY_Label = new Key<LWComponent,String>("label", KeyType.DATA) {
        @Override public void setValueWithContext(LWComponent c, String val, Object context) {
//            if (context == PROPERTY_SET_UNDO)
//                c.setLabelImpl(val, true, false);
//            else
//                c.setLabel(val);
        }
        @Override public String getValue(LWComponent c) { 
        	return c.getLabel(); 
        	}
    };
    public static final Key KEY_Notes = new Key<LWComponent,String>("notes", KeyType.DATA) {
        @Override public void setValue(LWComponent c, String val) { 
//        	c.setNotes(val); 
        	}
        @Override public String getValue(LWComponent c) { 
        	return c.getNotes(); 
        	}
    };
    
    public static final Key KEY_Collapsed =
            new Key<LWComponent,Boolean>("collapsed") {
            @Override public void setValue(LWComponent c, Boolean collapsed) {
//                c.setCollapsed(collapsed);
            }
            @Override public Boolean getValue(LWComponent c) {
                return c.isCollapsed() ? Boolean.TRUE : Boolean.FALSE;
            }
        };

        public static final Key KEY_Created =
            new Key<LWComponent,Long>("created") {
            @Override public Long getValue(LWComponent c) {
                return c.getCreated();
            }
            @Override public String getStringValue(LWComponent c) {
                return new Date(c.getCreated()).toString();
            }
        };
        
	public enum Alignment { LEFT, CENTER, RIGHT }
	public enum StrokeStyle {

        SOLID   (1,0),
            DOTTED (1,1),
            DASHED (2,2),
            DASH2 (3,2),
            DASH3 (5,3);

       private final float[] dashPattern = new float[2];

        StrokeStyle(float dashOn, float dashOff) {
            dashPattern[0] = dashOn; // pixels on (drawn)
            dashPattern[1] = dashOff; // pixels off (whitespace)
        }

        public BasicStroke makeStroke(double width) {
            return makeStroke((float) width);
        }

        public BasicStroke makeStroke(float width) {
            if (this == SOLID)
                return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
              //return new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
              //return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
            else
                return new BasicStroke(width
                                       , BasicStroke.CAP_BUTT // anything else will mess with the dash pattern
                                       , BasicStroke.JOIN_BEVEL
                                       , 10f // miter-limit
                                       //, 0f // miter-limit
                                       , dashPattern
                                       , 0f); // dash-phase (offset to start of pattern -- apparently pixels, not index)
        }
        // todo opt: better: could cache the strokes here for each dash pattern/size

    }
	static class PropertyValueVeto extends RuntimeException {
        PropertyValueVeto(String msg) {
            super(msg);
        }
    }
	
	protected abstract class Property<T> {

        final Key key;
        protected T value;

        Property(Key key) {
            this.key = key;
        }

        T get() { return value; }

        public void setTo(T newValue) {
            set(newValue);
        }

        boolean isChanged(T newValue)
        {
            if (this.value == newValue || (newValue != null && newValue.equals(this.value)))
                return false;
            else
                return true;
        }

        void set(T newValue) {
            //final Object old = get(); // if "get" actually does anything tho, this is a BAD idea; if needbe, create a "curValue"

            if (!isChanged(newValue))
                return;
            final Object oldValue = this.value;
            take(newValue);
            onChange();

            // RAISE CHANGE EVENT (for observers -- e.g., repaint, UndoManager, editors, etc)
            // maybe: if (alive()) ?
            LWComponent.this.notify(this.key, oldValue);

            // note: if could handle event raising in Key class, we could make this class static.
            // Tho then to bind to a particular property on a particular LWComponent, you'd need a
            // Binding object.  Do we actually bind directly to individual Property instance's
            // anywhere? (Answer: seems no -- was just able to make this class protected instead of
            // public) Could we possibly change class hierarchy such that LWComponent.Key is typed, and
            // subclassed for the various types, and then do away with the Property class hierarchy
            // entirely? -- SMF 2012
        }

        /** This JUST changes the stored value: no notifications of any kind will be triggered, no undo recorded. */
        void take(T o) {
            this.value = o;
//            if (DEBUG.TOOL) System.out.printf("     TAKING: %-30s -> %s\n", vtag(key, o, this), LWComponent.this);
        }

        /** impl's can override this to do something after the value has changed (after take() has been called),
         * and before listeners have been notified */
        void onChange() {}

        void setFromString(String s) {
            try {
                setBy(s);
            } catch (Throwable t) {
                Log.error("bad value for " + this + ": [" + s + "] " + t);
            }
        }

        void setFromCSS(String cssKey, String value) {
            throw new UnsupportedOperationException(this + " unimplemented setFromCSS " + cssKey + " = " + value);
            //VUE.Log.error("unimplemented setFromCSS " + cssKey + " = " + value);
        }

        void setBy(String fromValue) {
            // Could get rid all of the setBy's (and then mayve even all the StyleProp subclasses!!)
            // If we just had mapper class that took a type, a value, and returned a string (e.g., Font.class, Object value)
            Log.error("unimplememnted: " + this + " setBy " + fromValue.getClass() + " " + fromValue);
        }

        /** override to provide an impl other than value.toString() */
        String asString() {
            return value == null ? null : value.toString();
        }

        /*
        void setByUser(Object newValue) { // for tools.  Actually, tools using generic setProperty right now...
            out("SetByUser: " + key + " " + newValue);
            set(newValue);
        }
        */

        /** used for debugging */
        public String toString() {
            return key + "[" + value + "]";
        }

    }
    public class ColorProperty extends Property<java.awt.Color> {
        private static final short ALPHA_NOT_PERMITTED = Short.MIN_VALUE;
        private static final short NO_ALPHA_SET = -1;
        private short fixedAlpha = NO_ALPHA_SET;

        ColorProperty(Key key) { super(key); }
        ColorProperty(Key key, Color defaultValue) {
            this(key);
            this.value = defaultValue;
        }

        public boolean isTransparent() {
            return value == null || value.getAlpha() == 0;
        }

        public boolean isTranslucent() {
            return value == null || value.getAlpha() != 0xFF;
        }

        void setAllowAlpha(boolean allow) {
            if (allow)
                fixedAlpha = NO_ALPHA_SET;
            else
                fixedAlpha = ALPHA_NOT_PERMITTED;
        }

        /** alpha should be in the range 0-255 */
        void setFixedAlpha(int alpha) {
            if (alpha > 255)
                alpha = 255;
            else if (alpha < 0)
                alpha = 0;
            fixedAlpha = (short) alpha;
            //out("SET FIXED ALPHA " + fixedAlpha);
        }

        @Override
        void set(Color newColor) {

            if (fixedAlpha < 0) {
                // the common case
                super.set(newColor);
            } else {

                if (value == newColor)
                    return;

                // enforce the fixed alpha on any incoming color:
                if (newColor != null && newColor.getAlpha() != fixedAlpha && newColor.getAlpha() != 0) {
                    //out("COLOR VALUE: " + newColor + " " + ColorToString(newColor) + " alpha=" + newColor.getAlpha());
                    newColor = new Color((newColor.getRGB() & 0xFFFFFF) + (fixedAlpha << 24), true);
                    //out("used fixed alpha " + fixedAlpha + " producing " + newColor + " alpha=" + newColor.getAlpha()
                    //+ " " + ColorToString(newColor));
                }
                super.set(newColor);
            }
        }

        @Override
        void take(Color c) {
            if (fixedAlpha < NO_ALPHA_SET && (c == null || c.getAlpha() != 0xFF))
                throw new PropertyValueVeto(key + "; color with translucence: "
                                            + c
                                            + " alpha=" + c.getAlpha()
                                            + " not allowed on " + LWComponent.this);

//             if (LWComponent.this instanceof LWNode)
//                 super.take(c == null ? null : new Color(c.getRGB() + (fixedAlpha << 24), true));
//             //super.take(c == null ? null : new Color(c.getRGB() + ((128 & 0xFF) << 24), true));
//             //super.take(c == null ? null : new Color(c.getRGB() + 0x20000000, true));
//             else
            super.take(c);
        }

        @Override
        void setBy(String s) {
            set(StringToColor(s));
        }

        @Override
        void setFromCSS(String key, String value) {
            // todo: CSS Style object could include the already instanced Color object
            // we ignore key: assume that whatever it is is a color value
            setBy(value);
        }

        /** @return a value between 0.0 and 1.0 representing brightness: the saturation % of the strongest channel
         * e.g.: white returns 1, black returns 0
         */
        public float brightness() {
            return Util.brightness(value);
        }

//         dynamic version not workng
//         ///** @return the color, but with 50% alpha (half transparent) */
//         public final Color getWithAlpha(float alpha) {
//             return new Color(value.getRGB() + (((byte)(alpha*256)) << 6), true);
//             //return new Color(value.getRGB() + 0x80000000, true);
//         }

        public boolean equals(Color c) {
            return value == c || (c != null && c.equals(value));
        }


        String asString() {
            return ColorToString(get());
        }
    }
    abstract public class NumberProperty<T> extends Property<T> {
        NumberProperty(Key key) { super(key); }

        void setFromCSS(String cssKey, String value) {
            if (value.endsWith("pt") || value.endsWith("px"))
                setBy(value.substring(0, value.length()-2));
            else
                throw new IllegalArgumentException("unhandled CSS number conversion for [" + value + "]");

        }

    }
    private static final Float _DefaultFloat = new Float(0f);
    public class FloatProperty extends NumberProperty<java.lang.Float> {
        FloatProperty(Key key) {
            super(key);
            value = _DefaultFloat;
        }
        void setBy(String s) { set(new Float(s)); }
    }
    public class FontProperty extends Property<java.awt.Font> {
        FontProperty(Key key) {
            super(key);
            value = VueConstants.FONT_DEFAULT;
        }
        final void setBy(String s) {
        	//check for underline

        	String p = s.substring(s.indexOf("-")+1,s.length());
        	p = p.substring(0,p.indexOf("-"));

        	if (p.endsWith("underline"))
        	{	//do something
        		LWComponent.this.mFontUnderline.set("underline");
        		s= s.replaceAll(p, p.substring(0,p.indexOf("underline")));
        	}
        	Font f = Font.decode(s);

        	set(f);
        	}
        final String asString() {
            //if (this.font == null || this.font == getParent().getFont())
            //return null;

            final Font font = get();
            String strStyle;

            if (font.isBold()) {
                strStyle = font.isItalic() ? "bolditalic" : "bold";
            } else {
                strStyle = font.isItalic() ? "italic" : "plain";
            }

            if (LWComponent.this.mFontUnderline.get().equals("underline"))
            	strStyle = strStyle.concat("underline");
            return font.getName() + "-" + strStyle + "-" + font.getSize();
        }
    }
    private static final Integer _DefaultInteger = new Integer(0);
    public class IntProperty extends NumberProperty<java.lang.Integer> {
        IntProperty(Key key, Integer defaultValue) {
            super(key);
            value = defaultValue;
        }
        IntProperty(Key key) {
            this(key, _DefaultInteger);
        }

        void setBy(String s) { set(new Integer(s)); }
    }
    private static final String _DefaultString = "";
    public class StringProperty extends Property<java.lang.String> {
        StringProperty(Key key) {
            super(key);
            value = _DefaultString;
        }
        void setBy(String s) { set(s); }
    }
    public class EnumProperty<T extends Enum> extends Property<T> {
        EnumProperty(Key key, T defaultValue) {
            super(key);
            value = defaultValue;
            //System.out.println("enum values: " + Arrays.asList(defaultValue.getClass().getEnumConstants()));
            //System.out.println("enum test: " + Enum.valueOf(defaultValue.getClass(), "DASH1"));
        }
        void setBy(String s) {
            // note: value can never be null, or we'll need to store the Enum class reference elsewhere
            // (e.g., in the Key -- better there anyway, where we could provide a generic "values"
            // to list the supported values)
            set((T) Enum.valueOf(value.getClass(), s.trim()));
        }
    }
    public class CSSFontStyleProperty extends IntProperty {
        CSSFontStyleProperty(Key key) { super(key); }
        void setFromCSS(String cssKey, String value) {
            // todo: this ignoring the key, which will permit non-confomant CSS
            if ("italic".equalsIgnoreCase(value))
                set(java.awt.Font.ITALIC);
            else if ("bold".equalsIgnoreCase(value))
                set(java.awt.Font.BOLD);
            else
                set(0);
        }
    }
    public class CSSFontFamilyProperty extends StringProperty {
        CSSFontFamilyProperty(Key key) { super(key); }
        void setFromCSS(String cssKey, String value) {
            // no translation needed for now: just use the raw name -- if it's a preference list tho, we'll need to handle it
            setBy(value);
        }
    }
    
    /** Convenience: If key not a real Key (a String), always return true */
    public boolean supportsProperty(Object key) {
        if (key instanceof Key)
            return supportsProperty((Key)key);
        else
            return false;
    }

    /** @return true if the given property is currently supported on this component */
    public boolean supportsProperty(Key key) {
        return (mSupportedPropertyKeys & key.bit) != 0;
    }

	public enum KeyType { Default, STYLE, SUB_STYLE, DATA };
	public static class Key<TSubclass extends LWComponent,TValue> {
        /** A name for this key (used for undo labels & debugging) */
        public final String name;
        /** A name for a CSS property that can be used to initialize the value for this key */
        public final String cssName;
        /** The unique bit for this property key.
            (Implies a max of 64 keys that can be known as active to our tools -- use a BitSet if need more) */
        public final long bit;
//         /** True if this key for a style property -- a property that moves from style holders to LWCopmonents
//          * pointing to it via mParentStyle */
//         public final boolean isStyleProperty;

        public final KeyType type;

        public final boolean isColor;

        /* True this property is a sub-part of some other property */
        //public final boolean isSubProperty;

        public static final java.util.List<Key> AllKeys = new java.util.ArrayList<Key>();

        private static int InstanceCount; // increment for each key instance, to establish the appropriate bit
        private static final java.util.Map<Class,Long> ClassProperties = new java.util.HashMap<Class,Long>();

        /** Get the supported property bit mask for the given class in the LWComponent inheritance tree
         * This will only return accurate results after all Key's in the codebase have been initialized. */
        static long PropertyMaskForClass(Class<? extends LWComponent> clazz) {
            final Long bitsForClass = ClassProperties.get(clazz); // property bits for this class
            if (bitsForClass == null) {
                // If we found nothing, this must be the first instance of a new object
                // for some subclass of LWComponent that doesn't declare any of it's
                // own keys.  Merge the bits for all superclasses and put it in the
                // map for future reference.
                long propMaskForClass = 0L;
                for (Class c = clazz; c != null; c = c.getSuperclass())
                    propMaskForClass |= PartialPropertyMaskForClass(c);

                if (DEBUG.INIT) Log.debug(String.format("CACHED PROPERTY BITS for %s: %d", clazz, Long.bitCount(propMaskForClass)));
                ClassProperties.put(clazz, propMaskForClass);

                return propMaskForClass;
            } else
                return bitsForClass;
        }

        /** @return the currently stored property mask for the given class: only used during initialization
         * Will return 0L (no bit set) if the given class is not in the map (e.g., java.lang.Object)
         * This is used to disambiguate between properties that apply only to a particular
         * LWComponent subclass while we produce the ultimate merged results for all classes in
         * the hierarchy.
         */
        private static long PartialPropertyMaskForClass(Class clazz) {
            final Long bitsForClass = ClassProperties.get(clazz); // property bits for this class
            if (bitsForClass == null)
                return 0L;
            else
                return bitsForClass;
        }

        public boolean isStyleProperty() {
            return type == KeyType.STYLE;
        }

        public Key(String name) {
            this(name, KeyType.Default);
        }
        public Key(String name, KeyType keyType) {
            this(name, null, keyType);
        }
        public Key(String name, String cssName) {
            this(name, cssName, KeyType.STYLE);
        }

        protected Key(String name, String cssName, KeyType keyType) {
            this.name = name;
            this.cssName = cssName;
            this.type = keyType;
            this.isColor = name.endsWith("color"); // todo: hack -- make more explicit
            if (InstanceCount >= Long.SIZE) {
                this.bit = 0;
                tufts.Util.printStackTrace(Key.class + ": " + InstanceCount + "th key created -- need to re-implement (try BitSet)");
            } else
                this.bit = 1 << InstanceCount;
            AllKeys.add(this);

            // Note: this only works if the key is in fact declared in the enclosing class to
            // which it applies.  If we want to declare keys elsewhere, we'll need to add
            // a Class argument to the constructor.
            final Class clazz = getClass().getEnclosingClass(); // the class that own's the Key
            long propMaskForClass = (PartialPropertyMaskForClass(clazz) | bit); // add the new bit

            // Now be sure to mix in all properties found in all super-classes:
            for (Class c = clazz; c != null; c = c.getSuperclass())
                propMaskForClass |= PartialPropertyMaskForClass(c);

            ClassProperties.put(clazz, propMaskForClass);

            if (DEBUG.INIT || DEBUG.STYLE)
                Log.debug(String.format("KEY %-20s %-11s %-22s bit#%2d; %25s now has %2d properties",
                                        name,
                                        //isStyleProperty ? "STYLE;" : "",
                                        keyType,
                                        cssName == null ? "" : cssName,
                                        InstanceCount,
                                        clazz.getName(),
                                        Long.bitCount(propMaskForClass)
                                        ));
            InstanceCount++;

            //System.out.println("BITS FOR " + LWImage.class + " " + PropertyMaskForClass(LWImage.class));

            // Could build list of all key (and thus slot) values here for each subclass,
            // but where would we attach it?  Would need to pass in the class variable
            // in the constructor, and hash it to a list for the class.  Then the
            // problem would be that each list would only contain the subclass items,
            // not the super -- tho could we just iterate up through the supers getting
            // their lists to build the full list for each class?  (e.g., for duplicate,
            // persistance, or runtime diagnostic property editors)

            // OH: we also need to build the bitfield for the enclosing class:
            // the runtime-constant bit-mask representing all the properties
            // handled by this class / subclass of LWComponent

        }

        private static final LWComponent EmptyStyle = new LWComponent();
        static final Property NO_SLOT_PROVIDED = EmptyStyle.mFillColor; // any slot will do
        //private static final Property BAD_SLOT = EmptyStyle.mStrokeColor; // any (different) slot will do
        /** If this isn't overriden to return non-null, getValue & setValue must be overriden to provide the setter/getter impl  */
        Property getSlot(TSubclass c) { return NO_SLOT_PROVIDED; }

        boolean isSlotted(TSubclass c) { return getSlot(c) != NO_SLOT_PROVIDED; }

        // If we wanted to get rid of the slot decl's in the key's (for those that use
        // slots), we could, in our defult slot-using set/getValue, search all property
        // objects in the LWComponent, and if any of them match our key, we know that's
        // that slot, and if none of them do, then we have in internal error: coder
        // should have impl'd set/getValue themselves.

        /** non slot-based property keys can override this */
        TValue getValue(TSubclass c) {
            final Property propertySlot = getSlotSafely(c);
            try {
                if (propertySlot == NO_SLOT_PROVIDED) {
                    if (DEBUG.META)
                        Log.error(this + ";\n\tNo property, or: no slot, and getValue not overriden on client subclass:\n\t"
                                  + (c == null ? null : c.getClass()) + ";\n\t" + c, new Throwable());
                    else
                        Log.warn(c == null ? null : c.getClass() + "; has no property of type: " + this);
                    return null;
                } else
                    return (TValue) propertySlot.get();
            } catch (Throwable t) {
                if (DEBUG.META)
                    tufts.Util.printStackTrace(new Throwable(t), this + ": property slot get() failed " + propertySlot);
                else
                    Log.warn(this + ": property slot get() failed " + propertySlot + " " + t);
                return DEBUG.Enabled ? (TValue) "<unsupported for this object>" : null;
                //return null;
            }
        }

        // The design of the below two methods, setValue and setValueWithContext, is such that either
        // can be overridden depending on the needs of the Key implementation, but never BOTH.
        // Also, the setValue(TSubclass, TValue) method can never be called directly, except by
        // the single call made to it in setValueWithContext.  This is so that the context
        // argument can be completely ignored in simple Key implementations, and only made
        // use of where needed.

        /** non slot-based property keys can override this -- only override ONE of the two setValue methods -- note that this
         particular method should NOT EVER be called directly -- only overridden */
        // what we really want here is some kind of access modifier that make this protected for overriding, but private to calling
        protected void setValue(TSubclass c, TValue value) {
            setValueBySlot(c, value);
        }

        /** non slot-based property keys can override this -- only override ONE of the two setValue methods
         * Override the 2-argument version unless the context is needed */
        void setValueWithContext(TSubclass c, TValue value, Object context) {
            // if this has NOT been overriden (the usual case), defer to the default non-context setValue, which will defer to slot
            // based value-set if needed.
            setValue(c, value); // this is the only place that the 2-arg setValue should ever be called
        }

        private final void setValueBySlot(TSubclass c, TValue value) {
            final Property slot = getSlotSafely(c);
            if (slot == null || slot == NO_SLOT_PROVIDED)
                return;
            if (value instanceof String) {
                // If a String value comes in, this allows us to auto-parse it
                slot.setFromString((String)value);
            } else {
                slot.set(value);
            }
        }


        private Property getSlotSafely(TSubclass c) {
            Property slot = null;
            try {
                slot = getSlot(c);
            } catch (ClassCastException e) {
                String msg = "Property not supported: " + this + " on\t" + c + " (getSlot failed; returned null)";
                //tufts.Util.printStackTrace(e, msg);
                Log.warn(msg + "; " + e);
                return null;
            } catch (Throwable t) {
                tufts.Util.printStackTrace(new Throwable(t), this + ": bad slot? unimplemented get/setValue?");
                return null;
            }
            //if (slot == NO_SLOT_PROVIDED) tufts.Util.printStackTrace(this + ": no slot provided");
            return slot;
        }


        /** non slot-based property keys can override this */
        String getStringValue(TSubclass c) {
            final Property slot = getSlotSafely(c);
            if (slot == NO_SLOT_PROVIDED || slot == null) {
                // If there is no slot provided, we must get the value from the overridden
                // getter, getValue.
                Object typedValue = null;
                try {
                    // Call the overriden getValue:
                    typedValue = getValue(c);
                } catch (ClassCastException e) {
                    final String msg = "Property not supported(getStringValue): " + this + " on\t" + c;
                    if (DEBUG.META)
                        tufts.Util.printStackTrace(e, msg);
                    else
                        Log.warn(msg + "; " + e);
                    return DEBUG.Enabled ? "<unsupported for this object>" : null;
                }
                return typedValue == null ? null : typedValue.toString(); // produce something
//              } else if (slot == null) {
//                 // If a slot was provided, but it failed, no sense in trying
//                 // the default getValue, which presumably wasn't overriden if
//                 // a slot was provided.
//                 //tufts.Util.printStackTrace(this + ": bad slot");
//                 return DEBUG.Enabled ? "<unsupported for this object>" : null;
            } else
                return slot.asString();
        }

        void setStringValue(TSubclass c, String stringValue) {
            Property slot = getSlotSafely(c);
            if (slot != NO_SLOT_PROVIDED) {
                slot.setFromString(stringValue);
            } else {
                TValue v = getValue(c);
                // handle a few special cases for standard java types, even if there's no slot (Property object) to parse the string
                // This won't work if getValue returns null, as we'll have no class object to check for type information.
                     if (v instanceof String)    stringSet(c, stringValue);
                else if (v instanceof Integer)   stringSet(c, Integer.valueOf(stringValue));
                else if (v instanceof Long)      stringSet(c, Long.valueOf(stringValue));
                else if (v instanceof Float)     stringSet(c, Float.valueOf(stringValue));
                else if (v instanceof Double)    stringSet(c, Double.valueOf(stringValue));
                else if (v instanceof Boolean)   stringSet(c, Boolean.valueOf(stringValue));
                else
                    Log.error(this + ":setValue(" + stringValue + "); no slot provided for parsing string value", new Throwable("HERE"));
            }
        }

        private final void stringSet(TSubclass c, Object v) {
            setValueWithContext(c, (TValue) v, PROPERTY_SET_DEFAULT); // may want a seperate context here, e.g., PROPERTY_SET_FROM_STRING
        }

        /** @return true if was successful */
        boolean setValueFromCSS(TSubclass c, String cssKey, String cssValue) {
            final Property slot = getSlot(c);
            if (slot == Key.NO_SLOT_PROVIDED) {
//                c.out("Can't auto-apply CSS Style property to non-slotted key: " + cssName + " -> " + this);
                return false;
            }
            try {
                slot.setFromCSS(cssName, cssValue);
                System.err.println("applied value: " + slot);
                return true;
            } catch (Throwable t) {
                System.err.println();
                tufts.Util.printStackTrace(new Throwable(t), "failed to apply CSS key/value " + cssName + "=" + cssValue);
            }
            return false;
        }


        /** @return true if the value for this Key in LWComponent is equivalent to otherValue
         * Override to provide non-standard equivalence.
         * The default provided here uses Object.equals to compare the values.
         */
        boolean valueEquals(TSubclass c, Object otherValue)
        {
            final TValue value = getValue(c);
            return value == otherValue || (otherValue != null && otherValue.equals(value));
        }

        void copyValue(TSubclass source, TSubclass target)
        {
            if (!source.supportsProperty(this)) {
                if (DEBUG.STYLE && DEBUG.META) System.err.println("   COPY-VALUE: " + this + "; source doesn't support this property; " + source);
            } else if (!target.supportsProperty(this)) {
                if (DEBUG.STYLE && DEBUG.META) System.err.println("   COPY-VALUE: " + this + "; target doesn't support this property; " + target);
            } else {
                final TValue newValue = getValue(source);
                final TValue oldValue = getValue(target);

                if (newValue != oldValue && (newValue == null || !newValue.equals(oldValue))) {
                    if (DEBUG.STYLE) System.out.format("    COPY-VALUE: %s %s%-15s%s %-40s -> %s over (%s)\n",
                                                       source,
                                                       TERM_PURPLE,
                                                       this.name,
                                                       TERM_CLEAR,
                                                       //"(" + newValue + ")",
                                                       Util.tags(getStringValue(source)),
                                                       target,
                                                       oldValue);
                    setValueWithContext(target, newValue, PROPERTY_SET_DEFAULT); // may want a specific context here, e.g., PROPERTY_COPY
                }


                //if (DEBUG.STYLE) System.err.print(" COPY-VALUE: " + this + "(");
                //if (DEBUG.STYLE) System.err.println(copyValue + ") -> " + target);
            }
        }

        public String toString() { return name; } // must == name for now until tool panels handle new key objects (is this true yet?)
        //public String toString() { return type + "{" + name + "}"; }
    }
	
	public String getLabel() {
    	return this.label;
    }
	
	 public void setLabel(String label)
    {
        setLabelImpl(label, true, true);
    }
	 protected void setLabelImpl(String newLabel, final boolean setDocument, final boolean allowDataFill)
	 {
		this.label = newLabel;
	 }
	 public final LWContainer getParent() {
	        return this.parent;
	    }
	 
	 public static final boolean COLLAPSE_IS_GLOBAL = true;
	    public boolean isCollapsed() {
	    	
//	        if (COLLAPSE_IS_GLOBAL)
	            return false; // LWNode overrides
	        //return isGlobalCollapsed;
//	        else
//	            return hasFlag(Flag.COLLAPSED);
	    }
	    
	public static Color StringToColor(final String s)
    {
        if (s.trim().length() < 1)
            return null;

        Color c = null;
        try {
            c = VueResources.parseColor(s);
        } catch (NumberFormatException e) {
            tufts.Util.printStackTrace(new Throwable(e), "LWComponent.StringToColor[" + s + "]");
        }
        return c;
    }
	public static String ColorToString(final Color c)
    {
        // if null, or no hue and no alpha, return null
        //if (c == null || ((c.getRGB() & 0xFFFFFF) == 0 && c.getAlpha() == 255))
        if (c == null)
            return null;

        if (c.getAlpha() == 255) // opaque: only bother to save hue info
            return String.format("#%06X", c.getRGB() & 0xFFFFFF);
        else
            return String.format("#%08X", c.getRGB());
    }
	
	protected void notify(Key key, Object oldValue)
    {
//        if (alive())
//           notifyLWCListeners(new LWCEvent(this, this, key, oldValue));
    }
	
	public final void addChild(LWComponent c)
	{
		addChildren(Collections.singletonList(c), ADD_DEFAULT);
	}
	public final void dropChild(LWComponent c)
	{
		addChildren(Collections.singletonList(c), ADD_DROP);
	}
	public final void pasteChild(LWComponent c)
	{
		addChildren(Collections.singletonList(c), ADD_PASTE);
	}
	public final void addChildren(List<? extends LWComponent> children)
	{
		addChildren(children, ADD_DEFAULT);
	}
	public void addChildren(Collection<? extends LWComponent> children, Object context) {
        Util.printStackTrace(this + ": can't take children; ignored: " + 
        		Util.tags(children) + "; context=" + context);
    }
    public java.util.List<LWComponent> getChildren()
    {
        return NO_CHILDREN;
    }
    
	protected void setParent(LWContainer newParent)
    {
        //final boolean linkNotify = (!mXMLRestoreUnderway && parent != null);
        if (parent == newParent) {
            // This is normal.
            // (e.g., one case: during undo of reparenting operations)
            //if (DEBUG.Enabled) Util.printStackTrace("redundant set-parent in " + this + "; parent=" + newParent);
            return;
        }
//
//        if (newParent.hasAncestor(this)) {
//            Util.printStackTrace("ATTEMPTED PARENT LOOP " + this + " can't make a child our parent: " + newParent);
//            return;
//        }
//
//
//        if (!mXMLRestoreUnderway) {
//            // handled specially during restored
//            validateSchemaReference();
//        }
        //-----------------------------------------------------------------------------

        parent = newParent;

    }
	
	// --- draw; ---
	public void draw(DrawContext dc)
	{
		
	}
	public void drawZero(DrawContext dc)
	{
		
	}
	protected final void drawZeroDecorated(DrawContext dc, boolean drawSlides)
    {
		
    }
	public void drawFit(DrawContext dc, int borderGap) {}
	public void drawFit(DrawContext dc, Rectangle2D frame, int borderGap)
    {
    }
	public void drawLocal(DrawContext dc)
    {
		
    }
	public void drawImage(Graphics2D g, double alpha, Dimension maxSize, 
			Color fillColor, double zoomRequest)
    {
		
    }

	public void setLocation(float x, float y) {
		// TODO Auto-generated method stub
		setLocation(x, y, this, true);
	}
	protected void setLocation(float x, float y, LWComponent hearableEventSource, 
			boolean issueMapLocationChangeCalls)
    {
        if (this.x == x && this.y == y)
            return;

//        final Point2D.Float oldValue = new Point2D.Float(this.x, this.y);
//        takeLocation(x, y);
//
//        if (hearableEventSource != this)
//            hearableEventSource.notifyProxy(new LWCEvent(hearableEventSource, this, LWKey.Location, oldValue));
//        else //if (hearableEventSource != null) // if null, skip event delivery
//            notify(LWKey.Location, oldValue);
//
//        //        if (issueMapLocationChangeCalls && parent != null) {
//        if (issueMapLocationChangeCalls) {
//
//            final double scale;
//            if (parent != null)
//                scale = parent.getMapScale(); // we move within the scale of our parent
//            else
//                scale = 1.0;
//            if (DEBUG.LAYOUT) out("notifyMapLocationChanged: using scale " + scale);
//            notifyMapLocationChanged(this,
//                                     (x - oldValue.x) * scale,
//                                     (y - oldValue.y) * scale);
//        } else {
//            // this always needs to happen no matter what, even during undo
//            // (e.g., the shape of curves isn't stored anywhere -- always needs to be recomputed)
//            //if (!linkNotificationDisabled)
//            if (updatingLinks())
//                updateConnectedLinks(this);
//        }
    }
	
	public String getNotes() {
        return this.notes;
    }
	public long getCreated() {
        return mCreated;
    }
	public void setCreated(final long time) {
        //Log.debug(String.format("setCreated %s; %s", new Date(time), this));
        if (mCreated != 0){// && alive()) {
            Log.warn("setCreated erasing " + mCreated + "=" + new Date(mCreated)
                     + " with: " + time + "=" + new Date(time) + "; " + this);
        }
        mCreated = time;

    }

	    //private static int MapDepth;
    public LWMap getMap() {
        if (parent == null) {
            return null;
        } else {
//             if (++MapDepth >= 64) { // DEBUG
//                 Util.printStackTrace("PARENT LOOP at depth " + MapDepth);
//                 System.err.println("LWC: " + this);
//                 return null;
//             }
//             final LWMap m = parent.getMap();
//             MapDepth--;
//             return m;
            return parent.getMap();

        }
    }
}
